/* * #%L * P6Spy * %% * Copyright (C) 2002 - 2013 P6Spy * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package com.p6spy.engine.spy; import static org.junit.Assert.assertEquals; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.naming.NamingException; import javax.sql.DataSource; import javax.sql.XADataSource; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.NotSupportedException; import javax.transaction.RollbackException; import javax.transaction.SystemException; import javax.transaction.TransactionManager; import liquibase.exception.LiquibaseException; import org.apache.commons.beanutils.PropertyUtils; import org.eclipse.jetty.plus.jndi.Resource; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup; import bitronix.tm.BitronixTransactionManager; import bitronix.tm.TransactionManagerServices; import bitronix.tm.resource.jdbc.PoolingDataSource; import com.p6spy.engine.common.P6Util; import com.p6spy.engine.test.P6TestFramework; import com.p6spy.engine.test.P6TestLoadableOptions; import com.p6spy.engine.test.P6TestOptions; @RunWith(Parameterized.class) public class XADataSourceTest extends P6TestFramework { private static final Pattern URL_PATTERN = Pattern .compile("jdbc:([a-zA-Z0-9]+)://([a-zA-Z0-9]+)[:]?([0-9]*)/([a-zA-Z0-9]+)"); private TransactionManager tm; private List<Resource> jndiResources; private List<PoolingDataSource> poolingDSs; public XADataSourceTest(String db) throws SQLException, IOException { super(db); } @Parameters(name = "{index}: {0}") public static Collection<Object[]> dbs() { Collection<Object[]> result = new ArrayList<Object[]>(); for (Object o : P6TestFramework.dbs()) { // SQLite provides no datasource implementation => skip it if (!Arrays.equals(new Object[] { "SQLite" }, (Object[]) o)) { result.add((Object[]) o); } } return result; } @Before public void setUpXADataSourceTest() throws NamingException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException { final P6TestLoadableOptions testOptions = P6TestOptions.getActiveInstance(); jndiResources = new ArrayList<Resource>(); poolingDSs = new ArrayList<PoolingDataSource>(); tm = TransactionManagerServices.getTransactionManager(); // in test DS setup { final XADataSource realInTestDs = (XADataSource) P6Util.forName( testOptions.getXaDataSource().getClass().getName()).newInstance(); setXADSProperties(realInTestDs, testOptions.getUrl().replace(":p6spy", ""), testOptions.getUser(), testOptions.getPassword()); jndiResources.add(new Resource("jdbc/realInTestDs", realInTestDs)); final PoolingDataSource inTestDs = new PoolingDataSource(); inTestDs.setClassName(P6DataSource.class.getName()); inTestDs.setUniqueName("jdbc/inTestDs"); inTestDs.setMaxPoolSize(10); inTestDs.getDriverProperties().setProperty("realDataSource", "jdbc/realInTestDs"); inTestDs.setAllowLocalTransactions(true); inTestDs.init(); jndiResources.add(new Resource("jdbc/inTestDs", inTestDs)); poolingDSs.add(inTestDs); } // fixed DS setup { final XADataSource realFixedDs = (XADataSource) P6Util.forName("org.h2.jdbcx.JdbcDataSource") .newInstance(); setXADSProperties(realFixedDs, "jdbc:h2:mem:p6spy_realFixedDs", "sa", "sa"); jndiResources.add(new Resource("jdbc/realFixedDs", realFixedDs)); final PoolingDataSource fixedDs = new PoolingDataSource(); fixedDs.setClassName(P6DataSource.class.getName()); fixedDs.setUniqueName("jdbc/fixedDs"); fixedDs.setMaxPoolSize(10); fixedDs.getDriverProperties().setProperty("realDataSource", "jdbc/realFixedDs"); fixedDs.setAllowLocalTransactions(true); fixedDs.init(); jndiResources.add(new Resource("jdbc/fixedDs", fixedDs)); poolingDSs.add(fixedDs); } // liquibase opens it's own transaction => keep it out of ours try { P6TestUtil.setupTestData(new JndiDataSourceLookup().getDataSource("jdbc/inTestDs")); P6TestUtil.setupTestData(new JndiDataSourceLookup().getDataSource("jdbc/fixedDs")); } catch (LiquibaseException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { tm.begin(); // TODO move to liquibase? cleanData(new JndiDataSourceLookup().getDataSource("jdbc/inTestDs")); cleanData(new JndiDataSourceLookup().getDataSource("jdbc/fixedDs")); tm.commit(); } catch (NotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SystemException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (HeuristicMixedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (HeuristicRollbackException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (RollbackException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @After public void tearDownXADataSourceTest() { ((BitronixTransactionManager) tm).shutdown(); for (PoolingDataSource psd : poolingDSs) { try { psd.close(); } catch (Exception e) { e.printStackTrace(); } } poolingDSs = null; for (Resource resource : jndiResources) { try { resource.release(); } catch (Exception e) { e.printStackTrace(); } } jndiResources = null; } @Test public void twoPhaseCommitDataPersistedOnCommit() { try { tm.begin(); insertData(new JndiDataSourceLookup().getDataSource("jdbc/inTestDs")); insertData(new JndiDataSourceLookup().getDataSource("jdbc/fixedDs")); tm.commit(); tm.begin(); assertEquals(1, queryForInt(new JndiDataSourceLookup().getDataSource("jdbc/inTestDs"))); assertEquals(1, queryForInt(new JndiDataSourceLookup().getDataSource("jdbc/fixedDs"))); tm.commit(); } catch (NotSupportedException e) { e.printStackTrace(); Assert.fail(); } catch (SystemException e) { e.printStackTrace(); Assert.fail(); } catch (IllegalStateException e) { e.printStackTrace(); Assert.fail(); } catch (SecurityException e) { e.printStackTrace(); Assert.fail(); } catch (HeuristicMixedException e) { e.printStackTrace(); Assert.fail(); } catch (HeuristicRollbackException e) { e.printStackTrace(); Assert.fail(); } catch (RollbackException e) { e.printStackTrace(); Assert.fail(); } } @Test public void twoPhaseCommitDataNotPersistedOnRollback() { try { tm.begin(); insertData(new JndiDataSourceLookup().getDataSource("jdbc/inTestDs")); insertData(new JndiDataSourceLookup().getDataSource("jdbc/fixedDs")); tm.rollback(); tm.begin(); assertEquals(0, queryForInt(new JndiDataSourceLookup().getDataSource("jdbc/inTestDs"))); assertEquals(0, queryForInt(new JndiDataSourceLookup().getDataSource("jdbc/fixedDs"))); tm.commit(); } catch (NotSupportedException e) { e.printStackTrace(); Assert.fail(); } catch (SystemException e) { e.printStackTrace(); Assert.fail(); } catch (IllegalStateException e) { e.printStackTrace(); Assert.fail(); } catch (SecurityException e) { e.printStackTrace(); Assert.fail(); } catch (HeuristicMixedException e) { e.printStackTrace(); Assert.fail(); } catch (HeuristicRollbackException e) { e.printStackTrace(); Assert.fail(); } catch (RollbackException e) { e.printStackTrace(); Assert.fail(); } } private void cleanData(DataSource dataSource) { Connection connection = null; try { connection = dataSource.getConnection(); P6TestUtil.execute(connection, "delete from customers where id=50"); } catch (SQLException e) { e.printStackTrace(); Assert.fail(); } finally { try { if (null != connection) { connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } } private void insertData(DataSource ds) { Connection connection = null; try { connection = ds.getConnection(); P6TestUtil.execute(connection, "insert into customers(id,name) values (50,'foo')"); } catch (SQLException e) { e.printStackTrace(); Assert.fail(); } finally { try { if (connection != null) { connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } } private int queryForInt(DataSource ds) { Connection connection = null; try { connection = ds.getConnection(); // add different data to each connection return P6TestUtil.queryForInt(connection, "select count(*) from customers where id=50"); } catch (SQLException e) { e.printStackTrace(); Assert.fail(); } finally { try { if (connection != null) { connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } return -1; } public void setXADSProperties(XADataSource ds, String url, String userName, String password) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (url.contains(":derby:") || url.contains(":sqlfire:")) { PropertyUtils.setProperty(ds, "databaseName", "p6spy_xds"); PropertyUtils.setProperty(ds, "createDatabase", "create"); return; } else if (url.contains(":firebirdsql:")) { PropertyUtils.setProperty(ds, "databaseName", url.replace("jdbc:firebirdsql:", "")); } else if (PropertyUtils.isWriteable(ds, "URL")) { PropertyUtils.setProperty(ds, "URL", url); } else if (PropertyUtils.isWriteable(ds, "Url")) { PropertyUtils.setProperty(ds, "Url", url); } else if (PropertyUtils.isWriteable(ds, "url")) { PropertyUtils.setProperty(ds, "url", url); } else if (PropertyUtils.isWriteable(ds, "serverName") && PropertyUtils.isWriteable(ds, "portNumber") && PropertyUtils.isWriteable(ds, "databaseName") && URL_PATTERN.matcher(url).matches()) { final Matcher matcher = URL_PATTERN.matcher(url); if (!matcher.matches()) { throw new IllegalArgumentException("url in incorrect format: " + url); } final String host = matcher.group(2); final String port = matcher.group(3); final String db = matcher.group(4); PropertyUtils.setProperty(ds, "serverName", host); if (null != port && !port.isEmpty()) { PropertyUtils.setProperty(ds, "portNumber", Integer.parseInt(port)); } PropertyUtils.setProperty(ds, "databaseName", db); } else { throw new IllegalArgumentException( "Datasource imlpementation not supported by tests (yet) (for url setting): " + ds); } if (PropertyUtils.isWriteable(ds, "userName")) { PropertyUtils.setProperty(ds, "userName", userName); } else if (PropertyUtils.isWriteable(ds, "user")) { PropertyUtils.setProperty(ds, "user", userName); } else { throw new IllegalArgumentException( "Datasource imlpementation not supported by tests (yet) (for username setting): " + ds); } if (PropertyUtils.isWriteable(ds, "password")) { PropertyUtils.setProperty(ds, "password", password); } else { throw new IllegalArgumentException( "Datasource imlpementation not supported by tests (yet) (for password setting): " + ds); } } }